package com.leff.cards.entity;

import java.util.ArrayList;
import java.util.Random;

import com.google.appengine.api.datastore.Entity;
import com.leff.cards.CloudMessenger;
import com.leff.cards.Dictionary;
import com.leff.cards.StoreWrapper;

public class Group extends EntityWrapper {

	public static final String KIND = "Group";
	
	public static final String NAME = "name";
	public static final String MEMBERS = "members";
	public static final String MEMBER_HANDS = "member_hands";
	public static final String HISTORY = "history";
	public static final String ACTIVE_CARD = "active_card";
	public static final String VOTING_MEMBER = "voting_member";
	public static final String SUBMITTED_RESPONSES = "submitted_responses";
	public static final String SUBMITTERS = "submitters";
	
	public static final String QUERY_CARD_OFFSET = "query_card_offset";
	public static final String RESPONSE_CARD_OFFSET = "response_card_offset";
	public static final String SKIP_VALUE = "skip_prime";
	
	private static final int SKIP_PRIME = 71;
	private static final int CARDS_IN_A_HAND = 7;
	private static final int MAX_HISTORY_SIZE = 10;
	
	public Group(String groupName, User creator) {
		super(KIND);
		
		Random R = new Random();
		int offset = R.nextInt(StoreWrapper.getAllQueryCards().size());
		setQueryCardOffset(offset);
		
		offset = R.nextInt(StoreWrapper.getAllResponseCards().size());
		setResponseCardOffset(offset);
		setSkipValue(SKIP_PRIME);
		
		this.setActiveCard(nextQueryCard());
		
		setName(groupName);
		addMember(creator);
		setVotingMemberId(creator.getId());
	}
	public Group(Entity E) {
		super(E);
	}
	
	public void setName(String name) {
		put(NAME, name);
	}
	public String getName() {
		return (String)get(NAME);
	}
	
	public void setActiveCard(QueryCard card) {
		embed(ACTIVE_CARD, card);
	}
	public QueryCard getActiveCard() {
		return new QueryCard(extract(ACTIVE_CARD));
	}
	
	public void setVotingMemberId(long id) {
		put(VOTING_MEMBER, id);
	}
	public long getVotingMemberId() {
		return (Long)get(VOTING_MEMBER);
	}
	
	public void setQueryCardOffset(long offset) {
		put(QUERY_CARD_OFFSET, offset);
	}
	public long getQueryCardOffset() {
		return (Long)get(QUERY_CARD_OFFSET);
	}
	
	public void setResponseCardOffset(long offset) {
		put(RESPONSE_CARD_OFFSET, offset);
	}
	public long getResponseCardOffset() {
		return (Long)get(RESPONSE_CARD_OFFSET);
	}
	
	public void setSkipValue(long prime) {
		put(SKIP_VALUE, prime);
	}
	public long getSkipValue() {
		return (Long)get(SKIP_VALUE);
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<Long> getMemberIds() {
		
		ArrayList<Long> ids = (ArrayList<Long>)get(MEMBERS);
		if(ids == null) {
			ids = new ArrayList<Long>();
		}
		return ids;
	}
	public void addMember(User U) {
		ArrayList<Long> userIds = getMemberIds();
		ArrayList<String> userHands = getMemberHands();
		
		Long id = U.getId();
		if(!userIds.contains(id)) {
			userIds.add(id);
			put(MEMBERS, userIds);
			
			ArrayList<ResponseCard> cards = nextResponseCards(CARDS_IN_A_HAND);
			
			StringBuilder sb = new StringBuilder();
			boolean first = true;
			for(ResponseCard C : cards) {
				if(!first) {
					sb.append(",");
				}
				sb.append(C.getId());
				first = false;
			}
			
			userHands.add(sb.toString());
			put(MEMBER_HANDS, userHands);
		}
	}
	// Returns true if the group was altered and needs to be stored in the datastore
	public boolean removeMember(User U) {
		ArrayList<Long> ids = getMemberIds();
		int index = ids.indexOf(U.getId());
		if(index < 0) {
			return false;
		}
		
		ArrayList<String> hands = getMemberHands();
		
		ids.remove(index);
		
		if(ids.size() == 0) {
			StoreWrapper.delete(this);
			return false;
		}
		
		hands.remove(index);
		if(U.getId() == getVotingMemberId()) {
			long voterId = ids.get((index + 1) % ids.size());
			setVotingMemberId(voterId);
		}
		
		put(MEMBERS, ids);
		put(MEMBER_HANDS, hands);
		return true;
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<String> getMemberHands() {
		ArrayList<String> hands = (ArrayList<String>)get(MEMBER_HANDS);
		if(hands == null) {
			hands = new ArrayList<String>();
		}
		return hands;
	}
	
	public void submitCards(String cardIdsString, String userId) {
		ArrayList<String> submitters = getSubmitters();
		ArrayList<String> cards = getSubmittedCardStrings();
		
		// Submit cards
		int index = submitters.indexOf(userId);
		if(index < 0) {
			submitters.add(userId);
			cards.add(cardIdsString);
			
			// We have cards from everyone except the voter. Notify.
			if(cards.size() >= (getMemberIds().size() - 1)) {
				long voterId = this.getVotingMemberId();
				User U = StoreWrapper.getUserById(voterId);
				if(U != null) {
					CloudMessenger.sendVoteMessage(this, U);
				}
			}
		}
		else {
			cards.set(index, cardIdsString);
		}
		
		put(SUBMITTERS, submitters);
		put(SUBMITTED_RESPONSES, cards);
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<String> getSubmittedCardStrings() {
		ArrayList<String> submissions = (ArrayList<String>)get(SUBMITTED_RESPONSES);
		if(submissions == null) {
			submissions = new ArrayList<String>();
		}
		return submissions;
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<String> getSubmitters() {
		ArrayList<String> submitters = (ArrayList<String>)get(SUBMITTERS);
		if(submitters == null) {
			submitters = new ArrayList<String>();
		}
		return submitters;
	}
	
	public void selectSubmittedCards(String cardIdString) {
		
		ArrayList<String> submitters = getSubmitters();
		ArrayList<String> submissions = getSubmittedCardStrings();
		
		int index = 0;
		for(int i = 0; i < submissions.size(); i++) {
			String S = submissions.get(i);
			if(S.equals(cardIdString)) {
				index = i;
				break;
			}
		}
		
		Long winnerId = Long.valueOf(submitters.get(index));
		Long cardId = getActiveCard().getId();
		
		// Update history
		String history = HistoryItem.encode(cardId, winnerId, submissions, cardIdString);
		addHistory(history);
		
		// TODO: Update score for user?
		
		// Advance voting member
		ArrayList<Long> users = getMemberIds();
		long voterId = getVotingMemberId();
		for(int i = 0; i < users.size(); i++) {
			long member = users.get(i);
			if(voterId == member) {
				index = i;
				continue;
			}
			
			User U = StoreWrapper.getUserById(member);
			if(U == null) {
				continue;
			}
			if(member == winnerId) {
				CloudMessenger.sendRoundWonMessage(this, U);
			} else {
				CloudMessenger.sendNewRoundMessage(this, U);
			}
		}
		index = (index + 1) % users.size();
		
		this.setVotingMemberId(users.get(index));
		
		// Give all submitters new cards
		replaceSubmittedCards();
		
		// Clear submissions and submitters
		submissions.clear();
		submitters.clear();

		put(SUBMITTED_RESPONSES, submissions);
		put(SUBMITTERS, submitters);
		
		QueryCard qc = nextQueryCard();
		this.setActiveCard(qc);
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<String> getHistory() {

		ArrayList<String> history = (ArrayList<String>)get(HISTORY);
		if(history == null) {
			history = new ArrayList<String>();
		}
		
		return history;
	}
	public void addHistory(String historyString) {
		ArrayList<String> hist = getHistory();
		hist.add(historyString);
		put(HISTORY, hist);
	}
	
	private ArrayList<ResponseCard> nextResponseCards(int count) {
		
		ArrayList<ResponseCard> allResponses = StoreWrapper.getAllResponseCards();
		ArrayList<ResponseCard> cards = new ArrayList<ResponseCard>();
		
		int offset = (int)getResponseCardOffset();
		int skip = (int)getSkipValue();
		for(int i = 0; i < count; i++) {
			cards.add(allResponses.get(offset));
			offset = (offset + skip) % allResponses.size();
		}
		
		this.setResponseCardOffset((long)offset);
		
		return cards;
	}
	
	private QueryCard nextQueryCard() {
		
		ArrayList<QueryCard> allQueries = StoreWrapper.getAllQueryCards();
		
		int offset = (int)getQueryCardOffset();
		int skip = (int)getSkipValue();
		
		QueryCard QC = allQueries.get(offset);
		offset = (offset + skip) % allQueries.size();
		
		this.setQueryCardOffset((long)offset);
		
		return QC;
	}
	
	private void replaceSubmittedCards() {
		
		ArrayList<String> memberHands = getMemberHands();
		ArrayList<String> allSubmittedCards = StoreWrapper.explodeCommaSeparatedParameter(getSubmittedCardStrings());
		ArrayList<ResponseCard> newCards = nextResponseCards(allSubmittedCards.size());
		
		int newCardIndex = 0;
		
		// Loop over each member's hand
		for(int h = 0; h < memberHands.size(); h++) {
			
			String hand = memberHands.get(h);
			
			// Loop over each card in the hand
			String[] cards = hand.split(",");
			for(int i = 0; i < cards.length; i++) {
				
				String card = cards[i];
				// Check if that card matches a submitted card
				for(String submission : allSubmittedCards) {
					
					// Replace it
					if(card.equals(submission)) {
						cards[i] = "" + newCards.get(newCardIndex).getId();
						newCardIndex++;
						break;
					}
				}
			}
			
			hand = StoreWrapper.bindCardIdStrings(cards);
			memberHands.set(h, hand);
		}
		
		put(MEMBER_HANDS, memberHands);
	}
	
	public Dictionary toJSONForUserId(Long id) {
		
		Dictionary obj = new Dictionary();
		
		// Basic info
		obj.put(ID, getId());
		obj.put(NAME, getName());
		obj.put(ACTIVE_CARD, getActiveCard().toJSON());
		
		// History (Not actually done yet)
		ArrayList<Object> historyJson = new ArrayList<Object>();
		ArrayList<String> history = getHistory();
		int historyItems = history.size();
		
		for(int i = historyItems - 1; i >= 0 && i >= historyItems - MAX_HISTORY_SIZE; i--) {
			String S = history.get(i);
			HistoryItem HI = HistoryItem.decode(S);
			historyJson.add(HI.toJSON());
		}
		obj.put(HISTORY, historyJson);
		
		// JSON Strings for all Users in this group
		long voterId = this.getVotingMemberId();
		
		ArrayList<Object> membersJson = new ArrayList<Object>();
		ArrayList<User> users = StoreWrapper.getUsersById(getMemberIds());
		int userIndex = 0;
		int voterIndex = 0;
		
		for(int i = 0; i < users.size(); i++) {
			User U = users.get(i);
			membersJson.add(U.toJSON());
			
			if(U.getId() == id) {
				userIndex = i;
			}
			if(U.getId() == voterId) {
				voterIndex = i;
			}
		}
		obj.put(MEMBERS, membersJson);
		
		// We return a different set of cards if this user is in the voting position
		if(voterId == id) {
			obj.put("isVoter", true);
			
			// Put submitted response cards.
			ArrayList<String> cards = getSubmittedCardStrings();
			
			ArrayList<Object> cardsJson = new ArrayList<Object>();
			
			for(String cardString : cards) {
				
				ArrayList<ResponseCard> respCards = StoreWrapper.getResponseCardsWithIdString(cardString);
				
				ArrayList<Object> thisCardJson = new ArrayList<Object>();
				for(ResponseCard RC : respCards) {
					thisCardJson.add(RC.toJSON());
				}
				
				cardsJson.add(thisCardJson);
			}
			obj.put("cards", cardsJson);
			
			// Tell whether it's ready for a vote or not
			boolean ready = cards.size() >= users.size() - 1;
			obj.put("readyForVote", ready);
		}
		else {
			User voter = users.get(voterIndex);
			
			obj.put("isVoter", false);
			obj.put("voter", voter.getDisplayName());

			ArrayList<String> hands = getMemberHands();
			String allCardIds = hands.get(userIndex);
			
			ArrayList<ResponseCard> respCards = StoreWrapper.getResponseCardsWithIdString(allCardIds);
			
			ArrayList<Object> cardsJson = new ArrayList<Object>();
			for(ResponseCard RC : respCards) {
				cardsJson.add(RC.toJSON());
			}
			obj.put("cards", cardsJson);
			
			// Included whether or not we've submitted to this card yet
			ArrayList<String> submitters = getSubmitters();
			String idString = "" + id;
			if(submitters.contains(idString)) {
				obj.put("hasSubmitted", true);
			} else {
				obj.put("hasSubmitted", false);
			}
		}
				
		return obj;
	}
	
	public Dictionary toJSON() {
		
		Dictionary obj = new Dictionary();
		
		obj.put(ID, getId());
		obj.put(NAME, getName());
		
		ArrayList<Object> membersJson = new ArrayList<Object>();
		ArrayList<User> users = StoreWrapper.getUsersById(getMemberIds());
		for(int i = 0; i < users.size(); i++) {
			User U = users.get(i);
			membersJson.add(U.toJSON());
		}
		obj.put(MEMBERS, membersJson);
		
		return obj;
	}
}
